#include <linux/module.h>	/* Specifically, a module */
#include <linux/kernel.h>	/* We're doing kernel work */
#include <linux/proc_fs.h>	/* Necessary because we use the proc fs */
#include <linux/sched.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>

#define procfs_name "lfprng"

/**
 * This structure hold information about the /proc file
 *
 */
struct proc_dir_entry *Our_Proc_File;

typedef struct _thread_state_struct {
	pid_t pid;
	pid_t gid;
	long next_value;
	long seed_increment;
} prng_thread_state;

typedef struct _thread_state_node {
	struct _thread_state_node *next;
	prng_thread_state *thread;
} prng_thread_node;

typedef struct _group_struct {
	pid_t gid;
	int num_threads;
	prng_thread_node *head;
	int orig_seed;
} prng_group_struct;

typedef struct _prng_group_node {
	prng_group_struct *group;
	struct _prng_group_node *next;
} prng_group_node;

prng_group_struct* find_group(int gid);
int get_next_value(int curr_value, int seed_inc);
int construct_thread_group(prng_group_struct *group_leader);
int initialize_seed(prng_group_struct *group_leader, unsigned long seed);


prng_group_node *group_list_head;
static const int a = 764261123;
static const int c = 2147483647;

/*
 */
int procfile_read(char *buffer,
	      char **buffer_location,
	      off_t offset, int buffer_length, int *eof, void *data)
{
	int ret;
	
	printk(KERN_INFO "procfile_read (/proc/%s) called\n", procfs_name);
	
	/* 
	 */
	if (offset > 0) {
		/* we have finished to read, return 0 */
		ret  = 0;
	} else {
		/* fill the buffer, return the buffer size */
		ret = sprintf(buffer, "HelloWorld!\n");
	}

	return ret;
}

int procfile_write(struct file *filp,
		const char __user *buff,
		unsigned long len,
		void *data)
{
	int num_threads, ret;
	unsigned long seed;
	const char *kbuff;
	prng_group_struct *curr_group;


	//Move buff to kernel space, parse the string into a long
	kbuff = (char *) vmalloc(sizeof(char)*len);
	copy_from_user(kbuff, buff, len);
	seed = simple_strtol(kbuff, (kbuff+len), 10);

	//Check for group
	pid_t current_gid = current->tgid;

	if((curr_group = find_group(current_gid)))
	{
		if(!initialize_seed(curr_group, seed))
		{
			printk("WAS NOT ABLE TO SET THE SEED\n");
		}
	}
	else
	{
		//Set up group
		if(!construct_thread_group(curr_group))
		{
			printk("WAS NOT ABLE TO CONSTRUCT THREAD GROUP.\n");
		}
		if(!initialize_seed(curr_group, seed))
		{
			printk("WAS NOT ABLE TO SET THE SEED\n");
		}
	}
	return len;
}

int init_module()
{
	//0644 : Read/Write for file owner, read for everyone else
	Our_Proc_File = create_proc_entry(procfs_name, 0666, NULL);
	
	if (Our_Proc_File == NULL) {
		remove_proc_entry(procfs_name, &proc_root);
		printk(KERN_ALERT "Error: Could not initialize /proc/%s\n",
		       procfs_name);
		return -ENOMEM;
	}

	Our_Proc_File->read_proc = procfile_read;
	Our_Proc_File->write_proc = procfile_write;
	Our_Proc_File->owner 	 = THIS_MODULE;
	//Our_Proc_File->mode 	 = S_IFREG | S_IRUGO;
	//Our_Proc_File->uid 	 = 0;
	//Our_Proc_File->gid 	 = 0;
	//Our_Proc_File->size 	 = 37;

	printk(KERN_INFO "/proc/%s created\n", procfs_name);	
	return 0;	/* everything is ok */
}

void  cleanup_module()
{
	remove_proc_entry(procfs_name, &proc_root);
	printk(KERN_INFO "/proc/%s removed\n", procfs_name);
}

prng_group_struct* find_group(pid_t gid)
{
	prng_group_node *curr;
	char * buffer [255];
	sprintf(buffer, "Test find output 1.\n");
	curr = group_list_head;

	while(curr)
	{

	sprintf(buffer, "Test find output 2; loooop.\n");
		if(curr->group->gid == gid)
			return curr->group;

		curr = curr->next;
	sprintf(buffer, "Test output 3; still looop.\n");
	}

	return NULL;
}

int construct_thread_group(prng_group_struct* group_struct)
{
	int thread_count;
	char * buffer [255];
	struct list_head *sibling_list;
	struct task_struct *curr_sibling;
	prng_thread_state *curr_data;
	prng_thread_node *curr_thread_node;

	thread_count = 1;
	group_struct = (prng_group_struct*) vmalloc(sizeof(prng_group_struct));
	group_struct->gid = current->tgid;

	sprintf(buffer, "Test construct output 1.\n");
	//Create list of threads in this group
	//Initialize and set values for the head of the thread list
	curr_data = vmalloc(sizeof(prng_thread_state));
	curr_data->pid = current->pid;
	curr_data->gid = current->tgid;
	curr_thread_node = vmalloc(sizeof(prng_thread_node));
	curr_thread_node->thread = curr_data;
	curr_thread_node->next = NULL;

	sprintf(buffer, "Test construct output 2.\n");
	list_for_each(sibling_list, &(current->sibling))
	{
	sprintf(buffer, "Testing loooop.\n");
		//Set current sibling
		curr_sibling=list_entry(sibling_list, struct task_struct, sibling);

		//There was another thread, so increment the count
		thread_count++;

		//Create a next node in the thread list, and move curr to it
		curr_thread_node->next = vmalloc(sizeof(prng_thread_node));
		curr_thread_node = curr_thread_node->next;

		//Initialize the thread data for the new node, set its data
		curr_thread_node->thread = vmalloc(sizeof(prng_thread_state));
		curr_thread_node->thread->pid = curr_sibling->pid;
		curr_thread_node->thread->gid = curr_sibling->tgid;
	}
	
	sprintf(buffer, "Test output thread_count: %f.\n", thread_count);
	return thread_count;
}

int initialize_seed(prng_group_struct* group_member, unsigned long seed)
{
	return 1;
}

int get_next_value(int current_value, int seed_inc)
{
	int i, ret = current_value;

	for(i = 0; i < seed_inc; i++)
	{
		ret = (a * ret ) % c;
	}

	return ret;
}
